package ecomp.models;
import java.util.ArrayList;

import mac10.models.Cell;
import mac10.models.Point;
import mac10.models.WorldModel;

public class AStarHeap
{

    protected WorldModel world;
    protected Node start, goal;
    protected OpenHeap openlist;
    protected boolean avoidClosedFences, avoidOpenFences, avoidCows, avoidAgents;
    protected int[][] nodeStates;
    protected int lineOfAvoiding;

    protected ArrayList<Point> pointsToAvoid;
    
    protected static final int OPEN = 1;
    protected static final int CLOSED = 2;

    public AStarHeap (WorldModel world, Point start, Point goal)
    {
        this(world, start, goal, null);
    }
    
    public AStarHeap(WorldModel world, Point start, Point goal,
            ArrayList<Point> pointsToAvoid) {
        this.world = world;
        int fstart = 0 + start.maxNorm(goal);
        this.start = new Node(start, 0, fstart);
        this.goal = new Node(goal, Integer.MAX_VALUE, Integer.MAX_VALUE);
        this.lineOfAvoiding = 5;
        this.pointsToAvoid = pointsToAvoid;
    }

    /**
     * Calculates the path from start to goal If path was found, the path can be
     * reconstructed recursively with the predecessor of the goal-node
     * 
     * @return goal-node or null if no path was found
     */
    public Node AStar (long deadline)
    {
           this.openlist = new OpenHeap();
       this.nodeStates = new int[world.getWidth()][world.getHeight()];
        openlist.add(start);

        while (!openlist.isEmpty())
        {
            Node curr = openlist.poll();
            // System.out.println(curr);
            if (curr.equals(goal))
            {
                return curr;
            }
            expandNode(curr);

            nodeStates[curr.getX()][curr.getY()] = CLOSED;
        }
        return null;
    }

    protected void expandNode (Node curr)
    {
        ArrayList<Cell> cells = this.world.getNeighbours(curr.getX(), curr
                .getY());
        for (Cell cell : cells)
        {
            if (nodeStates[cell.getX()][cell.getY()] == CLOSED)
                continue;
            // these cells are never accessible
            if (cell.isObstacle() || cell.isSwitch())
                continue;

            if (cell.maxNorm(start) < lineOfAvoiding)
            {
                if (avoidClosedFences && cell.isFence() && !cell.isOpen())
                    continue;
                if (avoidOpenFences && cell.isFence() && cell.isOpen())
                    continue;
                if (avoidCows && cell.isCow())
                    continue;
                if (avoidAgents && (cell.isAllyAgent() || cell.isEnemyAgent()))
                    continue;
            }

            if (pointsToAvoid != null && pointsToAvoid.contains(cell)){
                continue;
            }
            
            int fsucc = calcFSucc(curr, cell);

            Node successor = new Node(cell, curr.getG() + 1, fsucc, curr);
            // System.out.println("\t"+successor+" f: "+fsucc);

            if (nodeStates[cell.getX()][cell.getY()] == OPEN)
            {
                openlist.updateNode(successor);
            }
            else
            {
                nodeStates[cell.getX()][cell.getY()] = OPEN;
                openlist.add(successor);
            }
        }
    }
    
        protected int calcFSucc(Node curr, Cell cell) {
                int fsucc;
                // Wenn moeglich sollen Fences nicht betreten werden
                if (cell.isFence()) {
                        fsucc = curr.getG() + 2 + goal.maxNorm(cell);
                } else {
                        fsucc = curr.getG() + 1 + goal.maxNorm(cell);
                }
                return fsucc;
        }

    /**
     * @return the avoidOpenFences
     */
    public boolean isAvoidOpenFences ()
    {
        return avoidOpenFences;
    }

    /**
     * @param avoidOpenFences
     *            the avoidFences to set
     */
    public void setAvoidOpenFences (boolean avoidOpenFences)
    {
        this.avoidOpenFences = avoidOpenFences;
    }

    /**
     * @return the avoidClosedFences
     */
    public boolean isAvoidClosedFences ()
    {
        return avoidClosedFences;
    }

    /**
     * @param avoidOpenFences
     *            the avoidFences to set
     */
    public void setAvoidClosedFences (boolean avoidClosedFences)
    {
        this.avoidClosedFences = avoidClosedFences;
    }

    /**
     * @return the avoidCows
     */
    public boolean isAvoidCows ()
    {
        return avoidCows;
    }

    /**
     * @param avoidCows
     *            the avoidCows to set
     */
    public void setAvoidCows (boolean avoidCows)
    {
        this.avoidCows = avoidCows;
    }

    /**
     * @return the avoidAgents
     */
    public boolean isAvoidAgents ()
    {
        return avoidAgents;
    }

    /**
     * @param avoidAgents
     *            the avoidAgents to set
     */
    public void setAvoidAgents (boolean avoidAgents)
    {
        this.avoidAgents = avoidAgents;
    }

    /**
     * @return the lineOfAvoiding
     */
    public int getLineOfAvoiding ()
    {
        return lineOfAvoiding;
    }

    /**
     * @param lineOfAvoiding
     *            the lineOfAvoiding to set
     */
    public void setLineOfAvoiding (int lineOfAvoiding)
    {
        this.lineOfAvoiding = lineOfAvoiding;
    }
}